using System;
using System.IO;
using System.Web;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections;
using System.Net;
using System.Collections.Generic;

namespace Asterisk.NET.FastAGI
{
	/// <summary>
	/// Default implementation of the AGIRequest interface.
	/// </summary>
	public class AGIRequest
	{
		#region Variables
#if LOGGER
		private Logger logger = Logger.Instance();
#endif
		private string rawCallerId;
		private Dictionary<string, string> request;

		/// <summary> A map assigning the values of a parameter (an array of Strings) to the name of the parameter.</summary>
		private Dictionary<string, List<string>> parameterMap;

		private string parameters;
		private string script;
		private bool callerIdCreated;
		private IPAddress localAddress;
		private int localPort;
		private IPAddress remoteAddress;
		private int remotePort;
		#endregion

		#region Constructor - AGIRequest(ICollection environment)
		/// <summary>
		/// Creates a new AGIRequest.
		/// </summary>
		/// <param name="environment">the first lines as received from Asterisk containing the environment.</param>
		public AGIRequest(List<string> environment)
		{
			if (environment == null)
				throw new ArgumentException("Environment must not be null.");
			request = buildMap(environment);
		}
		#endregion

		#region Request
		public IDictionary Request
		{
			get { return request; }
		}
		#endregion

		#region RequestURL 
		/// <summary>
		/// Returns the full URL of the request in the form agi://host[:port][/script].
		/// </summary>
		public string RequestURL
		{
			get { return (string) request["request"]; }
		}
		#endregion

		#region Channel 
		/// <summary>
		/// Returns the name of the channel.
		/// </summary>
		/// <returns>the name of the channel.</returns>
		public string Channel
		{
			get { return (string) request["channel"]; }
		}
		#endregion

		#region UniqueId 
		/// <summary>
		/// Returns the unqiue id of the channel.
		/// </summary>
		/// <returns>the unqiue id of the channel.</returns>
		public string UniqueId
		{
			get { return (string) request["uniqueid"]; }
		}
		#endregion

		#region Type 
		/// <summary>
		/// Returns the type of the channel, for example "SIP".
		/// </summary>
		/// <returns>the type of the channel, for example "SIP".</returns>
		public string Type
		{
			get { return (string) request["type"]; }
		}
		#endregion

		#region Language 
		/// <summary>
		/// Returns the language, for example "en".
		/// </summary>
		public string Language
		{
			get { return (string) request["language"]; }
		}
		#endregion

		#region CallerId 
		public string CallerId
		{
			get
			{
				string callerIdName = (string)(request["calleridname"]);
				string callerId = (string)(request["callerid"]);
				if (callerIdName != null)
				{
					if (callerId == null || callerId.ToLower(Helper.CultureInfo) == "unknown")
						return null;
					return callerId;
				}
				else // Asterisk 1.0
					return callerId10();
			}
		}
		#endregion

		#region CallerIdName 
		public string CallerIdName
		{
			get
			{
				string callerIdName = (string)(request["calleridname"]);
				if (callerIdName != null)
				{
					// Asterisk 1.2
					if (callerIdName.ToLower(Helper.CultureInfo) == "unknown")
						return null;
					return callerIdName;
				}
				else // Asterisk 1.0
					return callerIdName10();
			}
		}
		#endregion

		#region Asterisk 1.0 CallerID and CallerIdName 
		private string callerId10()
		{
			int lbPosition;
			int rbPosition;
				
			if (!callerIdCreated)
			{
				rawCallerId = ((string) request["callerid"]);
				callerIdCreated = true;
			}
				
			if (rawCallerId == null)
			{
				return null;
			}
				
			lbPosition = rawCallerId.IndexOf('<');
			rbPosition = rawCallerId.IndexOf('>');
				
			if (lbPosition < 0 || rbPosition < 0)
			{
				return rawCallerId;
			}
				
			return rawCallerId.Substring(lbPosition + 1, (rbPosition) - (lbPosition + 1));
		}

		private string callerIdName10()
		{
			int lbPosition;
			string callerIdName;
				
			if (!callerIdCreated)
			{
				rawCallerId = ((string) request["callerid"]);
				callerIdCreated = true;
			}
				
			if (rawCallerId == null)
				return null;
				
			lbPosition = rawCallerId.IndexOf('<');
				
			if (lbPosition < 0)
				return null;
				
			callerIdName = rawCallerId.Substring(0, (lbPosition) - (0)).Trim();
			if (callerIdName.StartsWith("\"") && callerIdName.EndsWith("\""))
				callerIdName = callerIdName.Substring(1, (callerIdName.Length - 1) - (1));
				
			if (callerIdName.Length == 0)
				return null;
			else
				return callerIdName;
		}
		#endregion

		#region Dnid 
		public string Dnid
		{
			get
			{
				string dnid = (string)(request["dnid"]);
				if (dnid == null || dnid.ToLower(Helper.CultureInfo) == "unknown")
					return null;
				return dnid; 
			}
		}
		#endregion

		#region Rdnis 
		public string Rdnis
		{
			get
			{
				string rdnis = (string)(request["rdnis"]);
				if (rdnis == null || rdnis.ToLower(Helper.CultureInfo) == "unknown")
					return null;
				return rdnis; 
			}
		}
		#endregion

		#region Context
		/// <summary>
		/// Returns the context in the dial plan from which the AGI script was called.
		/// </summary>
		public string Context
		{
			get { return (string) request["context"]; }
		}
		#endregion

		#region Extension
		/// <summary>
		/// Returns the extension in the dial plan from which the AGI script was called.
		/// </summary>
		public string Extension
		{
			get { return (string) request["extension"]; }
		}
		#endregion

		#region Priority
		/// <summary>
		/// Returns the priority in the dial plan from which the AGI script was called.
		/// </summary>
		public int Priority
		{
			get
			{
				if (request["priority"] != null)
				{
					return Int32.Parse((string) request["priority"]);
				}
				return -1;
			}

		}
		#endregion

		#region Enhanced 
		/// <summary>
		/// Returns wheather this agi is passed audio (EAGI - Enhanced AGI).<br/>
		/// Enhanced AGI is currently not supported on FastAGI.<br/>
		/// <code>true</code> if this agi is passed audio, <code>false</code> otherwise.
		/// </summary>
		public bool Enhanced
		{
			get
			{
				if (request["enhanced"] != null && (string)request["enhanced"] == "1.0")
						return true;
				return false;
			}
		}
		#endregion

		#region AccountCode 
		/// <summary>
		/// Returns the account code set for the call.
		/// </summary>
		public string AccountCode
		{
			get
			{
				return (string) request["accountCode"];
			}

		}
		#endregion

		#region LocalAddress
		public IPAddress LocalAddress
		{
			get { return localAddress; }
			set { this.localAddress = value; }
		}
		#endregion

		#region LocalPort
		public int LocalPort
		{
			get { return localPort; }
			set { this.localPort = value; }
		}
		#endregion

		#region RemoteAddress
		public IPAddress RemoteAddress
		{
			get { return remoteAddress; }
			set { this.remoteAddress = value; }
		}
		#endregion

		#region RemotePort
		public int RemotePort
		{
			get { return remotePort; }
			set { this.remotePort = value; }
		}
		#endregion

		#region Script()
		/// <summary>
		/// Returns the name of the script to execute.
		/// </summary>
		public string Script
		{
			get
			{
				if (script != null)
					return script;

				script = ((string)request["network_script"]);
				if (script != null)
				{
					Match scriptMatcher = Common.AGI_SCRIPT_PATTERN.Match(script);
					if (scriptMatcher.Success)
					{
						script = scriptMatcher.Groups[1].Value;
						parameters = scriptMatcher.Groups[2].Value;
					}
				}
				return script;
			}
		}
		#endregion

		#region CallingAni2
		public int CallingAni2
		{
			get
			{
				if (request["callingani2"] == null)
					return -1;
				try
				{
					return Int32.Parse((string)(request["callingani2"]));
				}
				catch {}
				return -1;
			}
		}
		#endregion

		#region CallingPres 
		public int CallingPres
		{
			get
			{
				if (request["callingpres"] == null)
					return -1;
				try
				{
					return Int32.Parse((string)(request["callingpres"]));
				}
				catch {}
				return -1;
			}
		}
		#endregion

		#region CallingTns 
		public int CallingTns
		{
			get
			{
				if (request["callingtns"] == null)
					return -1;
				try
				{
					return Int32.Parse((string)(request["callingtns"]));
				}
				catch {}
				return -1;
			}
		}
		#endregion

		#region CallingTon 
		public int CallingTon
		{
			get
			{
				if (request["callington"] == null)
					return -1;
				try
				{
					return Int32.Parse((String)(request["callington"]));
				}
				catch {}
				return -1;
			}
		}
		#endregion

		#region Parameter(string name) 
		public string Parameter(string name)
		{
			List<string> values;
			values = ParameterValues(name);
			if (values == null || values.Count == 0)
				return null;
			return values[0];
		}
		#endregion

		#region ParameterValues(string name) 
		public List<string> ParameterValues(string name)
		{
			if (ParameterMap().Count == 0)
				return null;
			
			return parameterMap[name];
		}
		#endregion

		#region ParameterMap() 
		public Dictionary<string, List<string>> ParameterMap()
		{
			if (parameterMap == null)
				parameterMap = parseParameters(this.parameters);
			return parameterMap;
		}
		#endregion

		#region ToString() 
		public override string ToString()
		{
			return Helper.ToString(this);
		}
		#endregion

		#region buildMap(ICollection lines) 
		/// <summary>
		/// Builds a map containing variable names as key (with the "agi_" prefix stripped) and the corresponding values.<br/>
		/// Syntactically invalid and empty variables are skipped.
		/// </summary>
		/// <param name="lines">the environment to transform.</param>
		/// <returns> a map with the variables set corresponding to the given environment.</returns>
		private Dictionary<string, string> buildMap(List<string> lines)
		{
			int colonPosition;
			string key;
			string value;

			Dictionary<string, string> map = new Dictionary<string,string>(lines.Count);
			foreach (string line in lines)
			{
				colonPosition = line.IndexOf(':');
				if (colonPosition < 0 || !line.StartsWith("agi_") || line.Length < colonPosition + 2)
					continue;

				key = line.Substring(4, colonPosition - 4).ToLower(Helper.CultureInfo);
				value = line.Substring(colonPosition + 2);
				if (value.Length != 0)
					map.Add(key, value);
			}
			return map;
		}
		#endregion

		#region parseParameters(string s)
		/// <summary>
		/// Parses the given parameter string and caches the result.
		/// </summary>
		/// <param name="s">the parameter string to parse</param>
		/// <returns> a Map made up of parameter names their values</returns>
		private Dictionary<string, List<string>> parseParameters(string parameters)
		{
			Dictionary<string, List<string>> result = new Dictionary<string, List<string>>();
			string name;
			string val;

			if (string.IsNullOrEmpty(parameters))
				return result;

			string[] pars = parameters.Split('&');
			if (pars.Length == 0)
				return result;

			foreach (string parameter in pars)
			{
				val = string.Empty;
				int i = parameter.IndexOf('=');
				if (i > 0)
				{
					name = HttpUtility.UrlDecode(parameter.Substring(0, i));
					if (parameter.Length > i + 1)
						val = HttpUtility.UrlDecode(parameter.Substring(i + 1));
				}
				else if (i < 0)
					name = HttpUtility.UrlDecode(parameter);
				else
					continue;

				if (!result.ContainsKey(name))
					result.Add(name, new List<string>());
				result[name].Add(val);
			}
			return result;
		}
		#endregion
	}
}